package com.joshua.spring.gateway.repo;

import com.joshua.spring.gateway.entity.dto.RouteDefinitionRefreshEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;


/**
 * description @Primary 路由缓存（意思是在众多相同的bean中，
 * 优先使用用@Primary注解的bean）,因为springCloudGateway
 * 默认InMemoryRouteDefinitionRepository
 * package com.joshua.spring.com.joshua.spring.gateway.service
 * classname  RedisRouteDefinitionRepository
 *
 * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
 * @date 2020/2/10 17:54
 */
@Slf4j
@Component(CachedRouteDefinitionRepository.CACHED_ROUTE_DEFINITION_REPOSITORY)
public class CachedRouteDefinitionRepository implements RouteDefinitionRepository, InitializingBean {
    public static final String CACHED_ROUTE_DEFINITION_REPOSITORY = "cachedRouteDefinitionRepository";
    /**
     * 路由表map<routeId,RouteDefinition>
     */
    private final Map<String, RouteDefinition> ROUTE_DEFINITIONS = Collections.synchronizedMap(new LinkedHashMap<>());

    @Autowired
    private RedisRouteDefinitionRepository redisRouteDefinitionRepository;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        //先从本地缓存取，本地取不到，从redis中取
        if (CollectionUtils.isEmpty(ROUTE_DEFINITIONS)) {
            init();
        }
        return Flux.fromIterable(ROUTE_DEFINITIONS.values());
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            ROUTE_DEFINITIONS.put(routeDefinition.getId(), routeDefinition);
            log.info("Save gateway route definition id [{}]", routeDefinition.getId());
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (ROUTE_DEFINITIONS.containsKey(id)) {
                ROUTE_DEFINITIONS.remove(id);
                log.info("Delete gateway route definition id [{}]", id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(
                    new NotFoundException(String.format("RouteDefinition not found:[%s]", routeId))));
        });
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }


    @EventListener
    public void refreshRouteDefinitions(RouteDefinitionRefreshEvent event) {
        log.info("Receive route refresh command,event->{}", event);
        clear();
        init();
    }

    /**
     * description: 从redis获取路由信息
     *
     * @author <a href="mailto:joshualwork@163.com">joshua_liu</a>
     * @date 2020/8/22 16:21
     */
    private void init() {
        log.info("Start reload route map ...");
        Map<String, RouteDefinition> initMap = redisRouteDefinitionRepository.pull();
        initMap.forEach((k, v) -> {
            log.info("reloading route map,add route:[routeId->{},route{}]", k, v);
            ROUTE_DEFINITIONS.put(k, v);
        });
        log.info("Reload route map finished ...");
    }

    private void clear() {
        ROUTE_DEFINITIONS.clear();
    }
}
